package migrator

import (
	"fmt"
	"gitee.com/xuyiping_admin/pkg/logger/logrus"
	"gorm.io/gorm"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

// SQL 文件正则匹配标记
var (
	shardingRegexp    = regexp.MustCompile(`-- Sharding=(\w+)`)
	shardingModRegexp = regexp.MustCompile(`-- ShardingMod=(\w+)`)
	skipAtRegexp      = regexp.MustCompile(`-- SkipAt:(.*)`)
)

func canSkip(str string) bool {
	group := skipAtRegexp.FindStringSubmatch(str)
	if len(group) < 2 {
		return false
	}

	env := os.Getenv("APP_ENVIRONMENT")
	envList := strings.Split(group[1], ",")
	for _, item := range envList {
		if strings.TrimSpace(item) == env && env != "" {
			return true
		}
	}
	return false
}

// eg. -- Sharding=true
func matchSharding(str string) bool {
	group := shardingRegexp.FindAllStringSubmatch(str, 1)
	if len(group) == 0 {
		return false
	}

	match := group[0]
	if len(match) <= 1 {
		return false
	}

	return strings.ToLower(match[1]) == "true"
}

// eg. -- ShardingMod=1024
func matchShardingMod(str string) int {
	group := shardingModRegexp.FindAllStringSubmatch(str, 1)
	if len(group) == 0 {
		return 0
	}

	match := group[0]
	if len(match) <= 1 {
		return 0
	}

	mod, err := strconv.Atoi(match[1])
	if err != nil || mod < 0 {
		return 0
	}

	if os.Getenv("APP_ENVIRONMENT") == "test" {
		return 2
	}

	return mod
}

func wrapSQLFile(file string) (*MigrationPlan, error) {
	// 使用 SQL 文件名作为迁移的 unique id
	name := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file))

	// 解析文件获取迁移计划
	blob, err := ioutil.ReadFile(file)
	if err != nil {
		return nil, err
	}

	// 使用正则匹配出注释中的标记
	content := string(blob)

	return &MigrationPlan{
		Name:        name,
		SQLs:        []string{content},
		Sharding:    matchSharding(content),
		ShardingMod: matchShardingMod(content),
		CanSkip:     canSkip(content),
	}, nil
}

func wrapMigrateStage(p MigrationPlan) MigrationStage {
	if p.CanSkip {
		return MigrationStage{
			Name:    p.Name,
			Migrate: wrapMigrateFunc([]string{}),
		}
	}

	var sqls []string
	if !p.Sharding || p.ShardingMod <= 0 {
		// none sharding
		sqls = p.SQLs
	} else {
		for _, sql := range p.SQLs {
			sharding, err := ShardingDDLSQLs(sql, p.ShardingMod)
			if err != nil {
				panic(err)
			}

			sqls = append(sqls, sharding...)
		}
	}

	return MigrationStage{
		Name:    p.Name,
		Migrate: wrapMigrateFunc(sqls),
	}
}

func wrapMigrateFunc(sqls []string) func(db *gorm.DB, name string) error {
	return func(db *gorm.DB, name string) error {
		return migrateSQLs(db, name, sqls)
	}
}

func migrateSQLs(db *gorm.DB, name string, sqls []string) error {
	for _, sql := range sqls {
		if err := exec(db, sql); err != nil {
			return err
		}
	}

	logrus.Infof("migrate %s", name)
	return nil
}

func exec(db *gorm.DB, sql string) error {
	if err := db.Exec(sql).Error; err != nil {
		return fmt.Errorf("migrate exec error: %s", err)
	}

	return nil
}
